package rexsee.core.storage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import rexsee.core.browser.Browser;
import rexsee.core.browser.clazz.JavascriptInterface;
import rexsee.core.browser.clazz.SyncTask;
import rexsee.core.browser.clazz.SyncTask.BooleanRunnable;
import rexsee.core.browser.clazz.SyncTask.StringRunnable;
import rexsee.core.utilities.Utilities;
import rexsee.security.RexseeSecurity;
import android.net.Uri;

public class RexseeZIP implements JavascriptInterface {

	public static final String INTERFACE_NAME = "ZIP";
	@Override
	public String getInterfaceName() {
		return mBrowser.application.resources.prefix + INTERFACE_NAME;
	}
	@Override
	public JavascriptInterface getInheritInterface(Browser childBrowser) {
		return this;
	}
	@Override
	public JavascriptInterface getNewInterface(Browser childBrowser) {
		return new RexseeZIP(childBrowser);
	}

	public static final int BUFF_SIZE = 1024 * 1024; // 1M Byte

	protected final Browser mBrowser;
	private int mCompressLevel = -1;

	public RexseeZIP(Browser browser) {
		mBrowser = browser;
	}

	protected boolean isValidPath(String path) {
		return true;
	}

	private String zip2string(String source, String encoding) {
		if (source == null) return "";
		try {
			return new String(source.getBytes("8859_1"), encoding);
		} catch (Exception e) {
			return source;
		}
	}

	private String entry2json(ZipEntry entry, String encoding) {
		try {
			String rtn = "{";
			rtn += "\"name\":\"" + zip2string(entry.getName(), encoding) + "\"";
			rtn += ",\"size\":" + entry.getSize();
			rtn += ",\"compressedSize\":" + entry.getCompressedSize();
			rtn += ",\"lastModification\":" + entry.getTime();
			rtn += ",\"isDirectory\":" + String.valueOf(entry.isDirectory());
			rtn += "}";
			return rtn;
		} catch (Exception e) {
			mBrowser.exception(getInterfaceName(), e);
			return "{}";
		}
	}
	public static boolean appendToZipArchive(File[] sourceFiles, ZipOutputStream zipout, String pathInZip) {
		for (int i = 0; i < sourceFiles.length; i++) {
			boolean rtn = appendToZipArchive(sourceFiles[i], zipout, pathInZip);
			if (!rtn) return false;
		}
		return true;
	}
	public static boolean appendToZipArchive(File sourceFile, ZipOutputStream zipout, String pathInZip) {
		try {
			pathInZip = pathInZip + (pathInZip.trim().length() == 0 ? "" : File.separator) + sourceFile.getName();
			if (sourceFile.isDirectory()) {
				File[] fileList = sourceFile.listFiles();
				for (File file : fileList) {
					if (!appendToZipArchive(file, zipout, pathInZip)) return false;
				}
			} else {
				byte buffer[] = new byte[BUFF_SIZE];
				BufferedInputStream in = new BufferedInputStream(new FileInputStream(sourceFile), BUFF_SIZE);
				zipout.putNextEntry(new ZipEntry(pathInZip));
				int realLength;
				while ((realLength = in.read(buffer)) != -1) {
					zipout.write(buffer, 0, realLength);
				}
				in.close();
				zipout.flush();
				zipout.closeEntry();
			}
			return true;
		} catch (Exception e) {
			//mBrowser.exception(getInterfaceName(), e);
			return false;
		}
	}

	//JavaScript Interface

	public void setLevel(int compressLevel) {
		mCompressLevel = (compressLevel < 0 || compressLevel > 9) ? -1 : compressLevel;
	}
	public int getLevel() {
		return mCompressLevel;
	}

	public String compress(String data) {
		byte[] bytes = data.getBytes();
		Deflater deflater = new Deflater(mCompressLevel);
		deflater.setInput(bytes);
		deflater.finish();
		byte[] tmp = new byte[bytes.length];
		int size = deflater.deflate(tmp);
		deflater.end();
		if (size == 0) return "";
		byte[] rtn = new byte[size];
		for (int i = 0; i < size; i++) {
			rtn[i] = tmp[i];
		}
		return RexseeSecurity.bytes2hex(rtn);
	}
	public String unCompress(String data) {
		byte[] bytes = RexseeSecurity.hex2bytes(data);
		Inflater inflater = new Inflater();
		inflater.setInput(bytes);
		byte result[] = new byte[0];
		ByteArrayOutputStream o = new ByteArrayOutputStream(1);
		try {
			byte[] buf = new byte[1024];
			int got = 0;
			while (!inflater.finished()) {
				got = inflater.inflate(buf);
				o.write(buf, 0, got);
			}
			inflater.end();
			result = o.toByteArray();
			o.close();
			return new String(result);
		} catch (Exception e) {
			mBrowser.exception(getInterfaceName(), e);
			inflater.end();
			try {
				o.close();
			} catch (IOException e2) {
			}
			return "";
		}
	}

	public boolean gzip(String source, String target) {
		return gzip(source, target, true, true);
	}
	public boolean gzip(final String source, final String target, boolean showProgress, boolean cancelable) {
		if (!isValidPath(source)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return false;
		}
		if (!isValidPath(target)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return false;
		}
		BooleanRunnable runnable = new BooleanRunnable() {
			@Override
			public boolean run() {
				byte[] bContent = Utilities.getContent(source);
				if (bContent == null) {
					mBrowser.exception(getInterfaceName(), "Error on reading source file.");
					return false;
				}
				File targetFile = Utilities.prepareWriteFile(target);
				if (targetFile == null) {
					mBrowser.exception(getInterfaceName(), "Error on creating GZIP file.");
					return false;
				}
				try {
					GZIPOutputStream gOut = new GZIPOutputStream(new FileOutputStream(targetFile));
					gOut.write(bContent);
					gOut.flush();
					gOut.finish();
					gOut.close();
					return true;
				} catch (Exception e) {
					mBrowser.exception(getInterfaceName(), e);
					return false;
				}
			}
		};
		if (showProgress) {
			return new SyncTask().run(runnable, mBrowser, cancelable);
		} else {
			return new SyncTask().run(runnable, null, false);
		}
	}
	public boolean unGzip(String source, String target) {
		return unGzip(source, target, true, true);
	}
	public boolean unGzip(final String source, final String target, boolean showProgress, boolean cancelable) {
		if (!isValidPath(source)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return false;
		}
		if (!isValidPath(target)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return false;
		}
		BooleanRunnable runnable = new BooleanRunnable() {
			@Override
			public boolean run() {
				File sourceFile = Utilities.prepareReadFile(source);
				if (sourceFile == null) {
					mBrowser.exception(getInterfaceName(), "Source file is not available.");
					return false;
				}
				File targetFile = Utilities.prepareWriteFile(target);
				if (targetFile == null) {
					mBrowser.exception(getInterfaceName(), "Error on creating target file.");
					return false;
				}
				try {
					GZIPInputStream in = new GZIPInputStream(new FileInputStream(sourceFile));
					OutputStream out = new FileOutputStream(targetFile);
					byte buffer[] = new byte[BUFF_SIZE];
					int realLength;
					while ((realLength = in.read(buffer)) > 0) {
						out.write(buffer, 0, realLength);
					}
					in.close();
					out.close();
					return true;
				} catch (Exception e) {
					mBrowser.exception(getInterfaceName(), e);
					return false;
				}
			}
		};
		if (showProgress) {
			return new SyncTask().run(runnable, mBrowser, cancelable);
		} else {
			return new SyncTask().run(runnable, null, false);
		}
	}

	public boolean zip(String source, String target) {
		return zip(source, target, true, true);
	}
	public boolean zip(final String sources, final String target, boolean showProgress, boolean cancelable) {
		if (!isValidPath(target)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return false;
		}
		BooleanRunnable runnable = new BooleanRunnable() {
			@Override
			public boolean run() {
				String[] sourceArray;
				if (sources.contains("|")) {
					sourceArray = sources.split("\\|");
				} else {
					sourceArray = new String[]{sources};
				}
				File[] sourceFiles = new File[sourceArray.length];
				for (int i = 0; i < sourceArray.length; i++) {
					String source = sourceArray[i];
					if (!isValidPath(source)) {
						mBrowser.exception(getInterfaceName(), "Invalid path.");
						return false;
					}
					if (!source.toLowerCase().startsWith("file://")) {
						mBrowser.exception(getInterfaceName(), "Invalid source file address: " + source);
						return false;
					}
					File sourceFile = new File(Uri.parse(source).getPath());
					if (!sourceFile.exists() || !sourceFile.canRead()) {
						mBrowser.exception(getInterfaceName(), "Source file [" + source + "] is not available.");
						return false;
					}
					sourceFiles[i] = sourceFile;
				}
				File zipFile = Utilities.prepareWriteFile(target);
				if (zipFile == null) {
					mBrowser.exception(getInterfaceName(), "Error on creating ZIP file.");
					return false;
				}
				try {
					ZipOutputStream zipout = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile), BUFF_SIZE));
					zipout.setLevel(mCompressLevel);
					boolean rtn = appendToZipArchive(sourceFiles, zipout, "");
					zipout.finish();
					zipout.close();
					return rtn;
				} catch (Exception e) {
					mBrowser.exception(getInterfaceName(), e.getLocalizedMessage());
					return false;
				}
			}
		};
		if (showProgress) {
			return new SyncTask().run(runnable, mBrowser, cancelable);
		} else {
			return new SyncTask().run(runnable, null, false);
		}
	}
	public String unZip(String source, String target, String encoding) {
		return unZip(source, target, encoding, null, true, true);
	}
	public String unZip(final String source, final String target, final String encoding, final String nameContains, boolean showProgress, boolean cancelable) {
		if (!isValidPath(source)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return null;
		}
		if (!isValidPath(target)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return null;
		}
		StringRunnable runnable = new StringRunnable() {
			@Override
			public String run() {
				File zipFile = Utilities.prepareReadFile(source);
				if (zipFile == null) {
					mBrowser.exception(getInterfaceName(), "Error on reading ZIP file.");
					return "[]";
				}
				try {
					String rtn = "";
					ZipFile zf = new ZipFile(zipFile);
					for (Enumeration<?> entries = zf.entries(); entries.hasMoreElements();) {
						ZipEntry entry = ((ZipEntry) entries.nextElement());
						if (nameContains != null && !entry.getName().contains(nameContains)) continue;
						if (entry.isDirectory()) continue;
						String path = target + File.separator + zip2string(entry.getName(), encoding);
						File desFile = Utilities.prepareWriteFile(path);
						if (desFile == null) {
							mBrowser.exception(getInterfaceName(), "Error on creating target file: " + path);
							return (rtn.equals("")) ? "[]" : rtn;
						}
						InputStream in = zf.getInputStream(entry);
						OutputStream out = new FileOutputStream(desFile);
						byte buffer[] = new byte[BUFF_SIZE];
						int realLength;
						while ((realLength = in.read(buffer)) > 0) {
							out.write(buffer, 0, realLength);
						}
						in.close();
						out.close();
						if (!rtn.equals("")) rtn += ",";
						rtn += entry2json(entry, encoding);
					}
					zf.close();
					return "[" + rtn + "]";
				} catch (Exception e) {
					mBrowser.exception(getInterfaceName(), e);
					return "[]";
				}
			}
		};
		if (showProgress) {
			return new SyncTask().run(runnable, mBrowser, cancelable);
		} else {
			return new SyncTask().run(runnable, null, false);
		}
	}
	public String list(String source, String encoding) {
		if (!isValidPath(source)) {
			mBrowser.exception(getInterfaceName(), "Invalid path.");
			return null;
		}
		File zipFile = Utilities.prepareReadFile(source);
		if (zipFile == null) {
			mBrowser.exception(getInterfaceName(), "Error on reading ZIP file: " + source);
			return "[]";
		}
		try {
			ZipFile zf = new ZipFile(zipFile);
			Enumeration<?> entries = zf.entries();
			String rtn = "";
			while (entries.hasMoreElements()) {
				if (!rtn.equals("")) rtn += ",";
				rtn += entry2json(((ZipEntry) entries.nextElement()), encoding);
			}
			return "[" + rtn + "]";
		} catch (Exception e) {
			mBrowser.exception(getInterfaceName(), e);
			return "[]";
		}
	}

}
